
//Thanks to A.R. Collins for writing CanvasStack-- a simple library for layering
//Canvases.

//A 2d array of colored boxes
var Mosaic = function(id,prefs){ //the id of a pre-created div on the page.  The canvas is created inside of this.
    return {
    init:
    function(){
      this.canvasStack =  new CanvasStack(id),
      this.sprlayer = document.getElementById(this.canvasStack.createLayer() ).getContext('2d');
      this.uilayer  = document.getElementById(this.canvasStack.createLayer() ).getContext('2d');
      this.xSize = this.canvasStack.bkgCvs.getAttribute('width');
      this.ySize = this.canvasStack.bkgCvs.getAttribute('height');
      for(var pref in prefs){ // load user preferences.
                              // valid prefs are `xSize`,`ySize`,`tileSize`,`xD`, and `yD`
        this[pref] = prefs[pref];
      }
    return this;
    },
    paintCoords: 
    function(x,y,color){
    //colors are stored & passed as Color objects. 
      var ts = this.tileSize;
      this.sprlayer.save();
      this.sprlayer.fillStyle = "rgba("+color.r+","+color.g+","+color.b+","+color.a+")";
      this.sprlayer.fillRect(this.xD+x*ts,this.yD+y*ts,ts,ts);
      this.sprlayer.restore(); 
    },

    coordsOf: function(xpos,ypos){
      var x = Math.floor( (xpos - this.xD)/this.tileSize); ///xD and yD are the offsets of the mosaic from the origin in pixels.
      var y = Math.floor( (ypos - this.yD)/this.tileSize); /// since sprited is designed to have one mosaic per canvas, there shouldn''t be much use for it.
      return [x,y]; //WARNING-- this function is NOT scroll-proof.  Going to fix that soon.
    },
    tileSize: 8,
    xD:0,
    yD:0,
  }.init();
}

var Selector = function(mosaic){
  var rectmanager = RectManager();
  return{
    select:
    function(x0,y0,xs,ys){//draws a box around the selected tile(s), and stores its coordinates.
      if(xs===undefined) xs=1;
      if(ys===undefined) ys=1;
      if(0 == rectmanager.makeRect(x0,y0,xs,ys) )
        this._drawBox(x0,y0,xs,ys); 
    },

    _drawBox:
    function(rect){
      var ts = mosaic.tileSize;
      mosaic.uilayer.save();
      mosaic.uilayer.lineWidth = 1;
      mosaic.uilayer.strokeRect(x0*ts+.5+mosaic.xD,y0*ts+.5+mosaic.yD,xs*ts,ys*ts);
      mosaic.uilayer.restore();
    },

    boxAt:
    function(x0,y0){
      rectmanager.rectAt(x0,y0); 
    },

    deselect:
    function(x0,y0){
      mosaic.uilayer.clearRect(0,0,mosaic.uilayer.canvas.width,mosaic.uilayer.canvas.height);
      for(s in rectmanager.rects){
        box = rectmanager.rect[s];
        this._drawBox(box.xOff,box.yOff,box.xSize,box.ySize);
      }
    },

  }
}

//An interface to zero or more rectangular areas(rects)
var RectManager = function() {
  return{
    rects: [],
    rectAt:
    //returns 
    function(xD,yD){
      for(var s in this.rects){
        if(xD   >= this.rects[s].xOff && xD<= this.rects[s].xOff + this.rects[s].xSize);
          if(yD >= this.rects[s].yOff && yD<= this.rects[s].yOff + this.rects[s].ySize);
            return this.rects[s];       
    
      }
      return -1;
    },
    removeRectAt:
    function(x0,y0){
      var sel = this.selected.indexOf(this.boxAt(x0,y0));
      delete this.selected[sel];
       
    },
    //creates a new `rect` and adds it to the manager.  Returns 0 if successful, and some other value if the rectangle would overlap another.
    makeRect:
    function(xD,yD,xS,yS){
      if(-1 == this.rectAt(xD,yD) ){
        this.rect.push({"xD":xD,"yD":yD,"xS":xS,"yS":yS});
        return 0;
      }
      else{
        return 1;
      } 
    }
  }

}
// src_x0,...,src_ys specify
// the origin and size of the rectangular area in the original image from which to pull pixels 
// the 'src_' prefix distinguishes them from the `x0` and `y0` args of `sprayImage`,
// which specify where the image should be painted on the canvas (in units of canvas "pixels").
// 
var ImageLoader = function(image,src_x0,src_y0,src_xs,src_ys){
// This object holds an image in `canvas` (which is invisible--
// you can't get at an image's pixels unless it's in a canvas), and has the ability to spray that image onto a mosaic
// with `loadImage`, at the mosaic "pixel" positions specified by `x0` and `y0`.
// it populates `pixels` with a 2d array of `Color` objects
//`canvas` is a hidden canvas, which we'll use for pixel manipulation.

  //load the whole image by default.
  if(src_x0 == undefined){src_x0 = 0;};
  if(src_y0 == undefined){src_y0 = 0;};
  if(src_xs == undefined){src_xs = image.width;};
  if(src_ys == undefined){src_ys = image.height;};

  var canvas = document.createElement('canvas');
  var ctx        = canvas.getContext('2d'); 
  var pixels = [];
  return {
    sprayImage: 
    function(mosaic,x0,y0){
      if(!x0) x0=0; //default image origin on the canvas is tile (0,0)
      if(!y0) y0=0;
      canvas.width  = image.width;  //Resize the canvas so it can hold the image.
      canvas.height = image.height; 
      ctx.drawImage(image,0,0);     //paint the image into our invisible canvas so can get at its pixels.
      var rawpixels = ctx.getImageData(0,0,canvas.width,canvas.height).data;
       // rawpixels = [r0,g0,b0,a0,r1,g1,b1,a1,...,rn,gn,bn,an] where n = w*h, as specified by W3C's ImageData object
      var color = {}; //
      var y = 0;
      var x = 0;
      for(var i=0;i<src_xs*src_ys*4; i+=4){
        if(x == src_xs){
          x  = 0;
          y += 1;
        }
        color.r = rawpixels[i];
        color.g = rawpixels[i+1];
        color.b = rawpixels[i+2];
        color.a = rawpixels[i+3];
        if(pixels[x] == undefined)  // create a new empty row if needed in `pixels`, our 2d array of rgba colors
          pixels[x] = {};
        pixels[x][y] = color;
        mosaic.paintCoords(x+x0,y+y0,color);
        x +=1;
      }
    }


  }
}


//formal definition of color "struct".  Need not be used to construct colors
var NewColor = function(r,g,b,a){
  color = {};
  color.r = r;
  color.g = g;
  color.b = b;
  color.a = a;
  return color;
}
var compareColors = function (color1,color2){
  if(color1.r == color2.r && color1.g == color2.g && color1.b == color2.b && color1.a == color2.a)
    return false;
  return true;
}

//A `Tile` is a managed area of mosaic space
var Tile = function(mosaic,x0,y0,xs,ys){
  return{
    paintCoords:
    function(x,y,color){
      if(x>=this.xSize || x<0 || y>=this.ySize || y<0)
        return false;
      mosaic.paintCoords(x+this.xOff,y+this.yOff,color)
    },
    coordsOf:
    function(xpos,ypos){
      var coords = mosaic.coordsOf(xpos,ypos);
      coords[0] -= this.xOff;
      coords[1] -= this.yOff;
      if(coords[0] <0 || coords[1]<0)
        return [-1,-1];
      return coords;
    },
    xOff:x0,
    yOff:y0,
    xSize:xs,
    ySize:ys
  }
}

//keeps track of tiles in the rectangular mosaic area defined by its arguments.
var TileManager = function(mosaic){

  return {
    tiles:[],
    newTileAt: 
    function(tx0,ty0,txs,tys){
      if(this.tileAt(tx0,ty0) != -1)
        return false;
      this._newTileAt(tx0,ty0,txs,tys);
    },
    tileAt: 
    function(tx0,ty0){
      for(var tile in this.tiles){
        if(tx0   >= this.tiles[tile].xOff && tx0<= this.tiles[tile].xOff + this.tiles[tile].xSize)
          if(ty0 >= this.tiles[tile].yOff && ty0<= this.tiles[tile].yOff + this.tiles[tile].ySize)
            return this.tiles[tile];
      }
      return -1;
    },
    removeTileAt:
    function(tx0,ty0){
      return delete this.tiles[this.tiles.indexOf(this.tileAt(tx0,ty0))] ;
    },
    _newTileAt:
    function(tx0,ty0,txs,tys){
      var t = Tile(mosaic,tx0,ty0,txs,tys);
      this.tiles.push(t);
    }
  }
  

}

var Sprited = function(prefs){
  var mos  = Mosaic("canvases",{"tileSize":16});
  selector = Selector(mos);
  imageloader = ImageLoader(document.getElementById("defaultimage"));
  imageloader.sprayImage(mos);
  var tileman = TileManager(mos,50,50,50,50);
  tileman.newTileAt(5,5,10,10);
  tileman.newTileAt(0,0,4,4);
  return {
    click:
    function(xpos,ypos){
      var coords = mos.coordsOf(xpos,ypos)
      var tile = tileman.tileAt(coords[0],coords[1]);
      var coords = tile.coordsOf(xpos,ypos);
      tile.paintCoords(coords[0],coords[1],{r:"128",g:"128",b:"128",a:"128"});
    },
  }
}

var draw = function(){
  app = Sprited();
  document.onmousedown =function(e){
    document.onmousemove = function(e){
      app.click(e.pageX-1,e.pageY-1);
    }
  }
  document.onmouseup = function(e){
    document.onmousemove = function(e){
      return false;
    }
  }

}
